/*
  ==============================================================================

   This file is part of the JUCE framework.
   Copyright (c) Raw Material Software Limited

   JUCE is an open source framework subject to commercial or open source
   licensing.

   By downloading, installing, or using the JUCE framework, or combining the
   JUCE framework with any other source code, object code, content or any other
   copyrightable work, you agree to the terms of the JUCE End User Licence
   Agreement, and all incorporated terms including the JUCE Privacy Policy and
   the JUCE Website Terms of Service, as applicable, which will bind you. If you
   do not agree to the terms of these agreements, we will not license the JUCE
   framework to you, and you must discontinue the installation or download
   process and cease use of the JUCE framework.

   JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
   JUCE Privacy Policy: https://juce.com/juce-privacy-policy
   JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/

   Or:

   You may also use this code under the terms of the AGPLv3:
   https://www.gnu.org/licenses/agpl-3.0.en.html

   THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
   WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.

  ==============================================================================
*/

#pragma once

#include "jucer_ColouredElement.h"
#include "../Properties/jucer_FontPropertyComponent.h"
#include "../Properties/jucer_JustificationProperty.h"

//==============================================================================
class PaintElementText   : public ColouredElement
{
public:
    PaintElementText (PaintRoutine* pr)
        : ColouredElement (pr, "Text", false, false),
          text ("Your text goes here"),
          font (FontOptions { 15.0f }),
          typefaceName (FontPropertyComponent::getDefaultFont()),
          justification (Justification::centred)
    {
        fillType.colour = Colours::black;
        position.rect.setWidth (200);
        position.rect.setHeight (30);
    }

    //==============================================================================
    void draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea) override
    {
        fillType.setFillType (g, getDocument(), parentArea);

        font = FontPropertyComponent::applyNameToFont (typefaceName, font);
        g.setFont (font);

        g.drawText (replaceStringTranslations (text, owner->getDocument()),
                    position.getRectangle (parentArea, layout), justification, true);
    }

    static String replaceStringTranslations (String s, JucerDocument* document)
    {
        s = s.replace ("%%getName()%%", document->getComponentName());
        s = s.replace ("%%getButtonText()%%", document->getComponentName());
        return s;
    }

    void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected) override
    {
        ColouredElement::getEditableProperties (props, multipleSelected);

        if (multipleSelected)
            return;

        props.add (new TextProperty (this));
        props.add (new FontNameProperty (this));
        props.add (new FontStyleProperty (this));
        props.add (new FontSizeProperty (this));
        props.add (new FontKerningProperty (this));
        props.add (new TextJustificationProperty (this));
        props.add (new TextToPathProperty (this));
    }

    void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) override
    {
        if (! fillType.isInvisible())
        {
            String x, y, w, h, r;
            positionToCode (position, code.document->getComponentLayout(), x, y, w, h);
            r << "{\n"
              << "    int x = " << x << ", y = " << y << ", width = " << w << ", height = " << h << ";\n"
              << "    juce::String text (" << quotedString (text, code.shouldUseTransMacro()) << ");\n"
              << "    " << fillType.generateVariablesCode ("fill")
              << "    //[UserPaintCustomArguments] Customize the painting arguments here..\n"
              << customPaintCode
              << "    //[/UserPaintCustomArguments]\n"
              << "    ";
            fillType.fillInGeneratedCode ("fill", position, code, r);
            r << "    g.setFont (" << FontPropertyComponent::getCompleteFontCode (font, typefaceName) << ");\n"
              << "    g.drawText (text, x, y, width, height,\n"
              << "                " << CodeHelpers::justificationToCode (justification) << ", true);\n"
              << "}\n\n";

            paintMethodCode += r;
        }
    }

    void applyCustomPaintSnippets (StringArray& snippets) override
    {
        customPaintCode.clear();

        if (! snippets.isEmpty() && ! fillType.isInvisible())
        {
            customPaintCode = snippets[0];
            snippets.remove (0);
        }
    }

    static const char* getTagName() noexcept        { return "TEXT"; }

    XmlElement* createXml() const override
    {
        XmlElement* e = new XmlElement (getTagName());
        position.applyToXml (*e);
        addColourAttributes (e);
        e->setAttribute ("text", text);
        e->setAttribute ("fontname", typefaceName);
        e->setAttribute ("fontsize", roundToInt (font.getHeight() * 100.0) / 100.0);
        e->setAttribute ("kerning", roundToInt (font.getExtraKerningFactor() * 1000.0) / 1000.0);
        e->setAttribute ("bold", font.isBold());
        e->setAttribute ("italic", font.isItalic());
        e->setAttribute ("justification", justification.getFlags());
        if (font.getTypefaceStyle() != "Regular")
        {
            e->setAttribute ("typefaceStyle", font.getTypefaceStyle());
        }

        return e;
    }

    bool loadFromXml (const XmlElement& xml) override
    {
        if (xml.hasTagName (getTagName()))
        {
            position.restoreFromXml (xml, position);
            loadColourAttributes (xml);

            text = xml.getStringAttribute ("text", "Hello World");
            typefaceName = xml.getStringAttribute ("fontname", FontPropertyComponent::getDefaultFont());
            font = FontPropertyComponent::applyNameToFont (typefaceName, font);
            font.setHeight ((float) xml.getDoubleAttribute ("fontsize", 15.0));
            font.setBold (xml.getBoolAttribute ("bold", false));
            font.setItalic (xml.getBoolAttribute ("italic", false));
            font.setExtraKerningFactor ((float) xml.getDoubleAttribute ("kerning", 0.0));
            justification = Justification (xml.getIntAttribute ("justification", Justification::centred));
            auto fontStyle = xml.getStringAttribute ("typefaceStyle");
            if (! fontStyle.isEmpty())
                font.setTypefaceStyle (fontStyle);

            return true;
        }

        jassertfalse;
        return false;
    }

    //==============================================================================
    const String& getText() const noexcept                           { return text; }

    class SetTextAction   : public PaintElementUndoableAction <PaintElementText>
    {
    public:
        SetTextAction (PaintElementText* const element, const String& newText_)
            : PaintElementUndoableAction <PaintElementText> (element),
              newText (newText_),
              oldText (element->getText())
        {
        }

        bool perform() override
        {
            showCorrectTab();
            getElement()->setText (newText, false);
            return true;
        }

        bool undo() override
        {
            showCorrectTab();
            getElement()->setText (oldText, false);
            return true;
        }

    private:
        String newText, oldText;
    };

    void setText (const String& t, const bool undoable)
    {
        if (t != text)
        {
            if (undoable)
            {
                perform (new SetTextAction (this, t),
                         "Change text element text");
            }
            else
            {
                text = t;
                changed();
            }
        }
    }

    //==============================================================================
    const Font& getFont() const                                     { return font; }

    class SetFontAction   : public PaintElementUndoableAction <PaintElementText>
    {
    public:
        SetFontAction (PaintElementText* const element, const Font& newFont_)
            : PaintElementUndoableAction <PaintElementText> (element),
              newFont (newFont_),
              oldFont (element->getFont())
        {
        }

        bool perform() override
        {
            showCorrectTab();
            getElement()->setFont (newFont, false);
            return true;
        }

        bool undo() override
        {
            showCorrectTab();
            getElement()->setFont (oldFont, false);
            return true;
        }

    private:
        Font newFont, oldFont;
    };

    void setFont (const Font& newFont, const bool undoable)
    {
        if (font != newFont)
        {
            if (undoable)
            {
                perform (new SetFontAction (this, newFont),
                         "Change text element font");
            }
            else
            {
                font = newFont;
                changed();
            }
        }
    }

    //==============================================================================
    class SetTypefaceAction   : public PaintElementUndoableAction <PaintElementText>
    {
    public:
        SetTypefaceAction (PaintElementText* const element, const String& newValue_)
            : PaintElementUndoableAction <PaintElementText> (element),
              newValue (newValue_),
              oldValue (element->getTypefaceName())
        {
        }

        bool perform() override
        {
            showCorrectTab();
            getElement()->setTypefaceName (newValue, false);
            return true;
        }

        bool undo() override
        {
            showCorrectTab();
            getElement()->setTypefaceName (oldValue, false);
            return true;
        }

    private:
        String newValue, oldValue;
    };

    void setTypefaceName (const String& newFontName, const bool undoable)
    {
        if (undoable)
        {
            perform (new SetTypefaceAction (this, newFontName),
                     "Change text element typeface");
        }
        else
        {
            typefaceName = newFontName;
            changed();
        }
    }

    String getTypefaceName() const noexcept                         { return typefaceName; }

    //==============================================================================
    Justification getJustification() const noexcept                 { return justification; }

    class SetJustifyAction   : public PaintElementUndoableAction <PaintElementText>
    {
    public:
        SetJustifyAction (PaintElementText* const element, Justification newValue_)
            : PaintElementUndoableAction <PaintElementText> (element),
              newValue (newValue_),
              oldValue (element->getJustification())
        {
        }

        bool perform() override
        {
            showCorrectTab();
            getElement()->setJustification (newValue, false);
            return true;
        }

        bool undo() override
        {
            showCorrectTab();
            getElement()->setJustification (oldValue, false);
            return true;
        }

    private:
        Justification newValue, oldValue;
    };

    void setJustification (Justification j, const bool undoable)
    {
        if (justification.getFlags() != j.getFlags())
        {
            if (undoable)
            {
                perform (new SetJustifyAction (this, j),
                         "Change text element justification");
            }
            else
            {
                justification = j;
                changed();
            }
        }
    }

    void convertToPath()
    {
        if (PaintRoutineEditor* parent = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
        {

            font = FontPropertyComponent::applyNameToFont (typefaceName, font);

            const Rectangle<int> r =
                getCurrentBounds (parent->getComponentArea().withZeroOrigin());

            GlyphArrangement arr;
            arr.addCurtailedLineOfText (font, text,
                                        0.0f, 0.0f, (float) r.getWidth(),
                                        true);

            arr.justifyGlyphs (0, arr.getNumGlyphs(),
                               (float) r.getX(), (float) r.getY(),
                               (float) r.getWidth(), (float) r.getHeight(),
                               justification);

            Path path;
            arr.createPath (path);

            convertToNewPathElement (path);
        }
        else
        {
            jassertfalse;
        }
    }

private:
    String text;
    Font font;
    String typefaceName;
    Justification justification;
    String customPaintCode;

    Array <Justification> justificationTypes;

    //==============================================================================
    class TextProperty  : public TextPropertyComponent,
                          private juce::ChangeListener
    {
    public:
        TextProperty (PaintElementText* const e)
            : TextPropertyComponent ("text", 2048, false),
              element (e)
        {
            element->getDocument()->addChangeListener (this);
        }

        ~TextProperty() override
        {
            element->getDocument()->removeChangeListener (this);
        }

        void setText (const String& newText) override    { element->setText (newText, true); }
        String getText() const override                  { return element->getText(); }

    private:
        void changeListenerCallback (ChangeBroadcaster*) override     { refresh(); }

        PaintElementText* const element;
    };

    //==============================================================================
    class FontNameProperty  : public FontPropertyComponent,
                              private juce::ChangeListener
    {
    public:
        FontNameProperty (PaintElementText* const e)
            : FontPropertyComponent ("font"),
              element (e)
        {
            element->getDocument()->addChangeListener (this);
        }

        ~FontNameProperty() override
        {
            element->getDocument()->removeChangeListener (this);
        }

        void setTypefaceName (const String& newFontName) override    { element->setTypefaceName (newFontName, true); }
        String getTypefaceName() const override                      { return element->getTypefaceName(); }

    private:
        void changeListenerCallback (ChangeBroadcaster*) override                 { refresh(); }

        PaintElementText* const element;
    };

    //==============================================================================
    class FontStyleProperty  : public ChoicePropertyComponent,
                               private juce::ChangeListener
    {
    public:
        FontStyleProperty (PaintElementText* const e)
            : ChoicePropertyComponent ("style"),
              element (e)
        {
            element->getDocument()->addChangeListener (this);

            updateStylesList (element->getTypefaceName());
        }

        ~FontStyleProperty() override
        {
            element->getDocument()->removeChangeListener (this);
        }

        void updateStylesList (const String& name)
        {
            if (getNumChildComponents() > 0)
            {
                if (auto cb = dynamic_cast<ComboBox*> (getChildComponent (0)))
                    cb->clear();

                getChildComponent (0)->setVisible (false);
                removeAllChildren();
            }

            choices.clear();

            choices.add ("Regular");
            choices.add ("Bold");
            choices.add ("Italic");
            choices.add ("Bold Italic");

            choices.mergeArray (Font::findAllTypefaceStyles (name));
            refresh();
        }

        void setIndex (int newIndex) override
        {
            Font f (element->getFont());

            if (f.getAvailableStyles().contains (choices[newIndex]))
            {
                f.setBold   (false);
                f.setItalic (false);
                f.setTypefaceStyle (choices[newIndex]);
            }
            else
            {
                f.setTypefaceStyle ("Regular");
                f.setBold   (newIndex == 1 || newIndex == 3);
                f.setItalic (newIndex == 2 || newIndex == 3);
            }

            element->setFont (f, true);
        }

        int getIndex() const override
        {
            auto f = element->getFont();

            const auto typefaceIndex = choices.indexOf (f.getTypefaceStyle());
            if (typefaceIndex == -1)
            {
                if (f.isBold() && f.isItalic())
                    return 3;
                else if (f.isBold())
                    return 1;
                else if (f.isItalic())
                    return 2;

                return 0;
            }

            return typefaceIndex;
        }

    private:
        void changeListenerCallback (ChangeBroadcaster*) override
        {
            updateStylesList (element->getTypefaceName());
        }

        PaintElementText* const element;
    };

    //==============================================================================
    class FontSizeProperty  : public SliderPropertyComponent,
                              private juce::ChangeListener
    {
    public:
        FontSizeProperty (PaintElementText* const e)
            : SliderPropertyComponent ("size", 1.0, 250.0, 0.1, 0.3),
              element (e)
        {
            element->getDocument()->addChangeListener (this);
        }

        ~FontSizeProperty() override
        {
            element->getDocument()->removeChangeListener (this);
        }

        void setValue (double newValue) override
        {
            element->getDocument()->getUndoManager().undoCurrentTransactionOnly();

            Font f (element->getFont());
            f.setHeight ((float) newValue);

            element->setFont (f, true);
        }

        double getValue() const override
        {
            return element->getFont().getHeight();
        }

    private:
        void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }

        PaintElementText* const element;
    };

    //==============================================================================
    class FontKerningProperty  : public SliderPropertyComponent,
                                 private juce::ChangeListener
    {
    public:
        FontKerningProperty (PaintElementText* const e)
            : SliderPropertyComponent ("kerning", -0.5, 0.5, 0.001),
              element (e)
        {
            element->getDocument()->addChangeListener (this);
        }

        ~FontKerningProperty() override
        {
            element->getDocument()->removeChangeListener (this);
        }

        void setValue (double newValue) override
        {
            element->getDocument()->getUndoManager().undoCurrentTransactionOnly();

            Font f (element->getFont());
            f.setExtraKerningFactor ((float) newValue);

            element->setFont (f, true);
        }

        double getValue() const override
        {
            return element->getFont().getExtraKerningFactor();
        }

    private:
        void changeListenerCallback (ChangeBroadcaster*) override
        {
            refresh();
        }

        PaintElementText* const element;
    };

    //==============================================================================
    class TextJustificationProperty  : public JustificationProperty,
                                       private juce::ChangeListener
    {
    public:
        TextJustificationProperty (PaintElementText* const e)
            : JustificationProperty ("layout", false),
              element (e)
        {
            element->getDocument()->addChangeListener (this);
        }

        ~TextJustificationProperty() override
        {
            element->getDocument()->removeChangeListener (this);
        }

        void setJustification (Justification newJustification) override
        {
            element->setJustification (newJustification, true);
        }

        Justification getJustification() const override
        {
            return element->getJustification();
        }

    private:
        void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }

        PaintElementText* const element;
    };

    //==============================================================================
    class TextToPathProperty  : public ButtonPropertyComponent
    {
    public:
        TextToPathProperty (PaintElementText* const e)
            : ButtonPropertyComponent ("path", false),
              element (e)
        {
        }

        void buttonClicked() override
        {
            element->convertToPath();
        }

        String getButtonText() const override
        {
            return "convert text to a path";
        }

    private:
        PaintElementText* const element;
    };
};
